Both named and anonymous call are optimized for calls where the number of arguments is known at compile time. Unknown argument calls are a pathological case of anonymous call; this case will be ignored in the main discussion. The difference between named and anonymous calls is in the argument count dispatching mechanism.
Named call allows an arbitrary number of entry points, with start PCs at arbitrary locations in the code vector. The link-table mechanism described below allows named calls to jump directly to the actual entry point without any run-time argument count or type checking checking.
Anonymous call has a fixed number of entry points, with start PCs at fixed locations in the code vector. This allows calls to be made without knowing what function is being called, but has more run-time overhead. The object called must be checked to be a valid function-entry object. The entry PC must be computed from the function entry, and argument count checking must be done if there are more than three required or optional arguments.
Argument passing in full call is conceptually similar to local call, but the caller can't allocate the entire frame for the callee, since it doesn't know how much stack is needed. Instead we allocate the frame in two parts. The caller only allocates the beginning of the frame, which contains the stack arguments in fixed locations. We leave the first <n> locations unused so that the called function can move register more args onto the stack without having to BLT down any stack arguments.
The place in the code where a full call jumps in is called an external entry point. The external entry point allocates the rest of the stack frame and then does a local call to the actual entry-point function, fetching the arguments from the standard passing locations. Usually we can do a tail-recursive local call.
There are two main cases where the call from the external entry point cannot be tail-recursive: – It is desirable to use the known-values convention for calling the entry-point function if the entry-point is used in other local calls (perhaps because of recursion). In this case, the called function stores the return values back into the frame allocated by the external entry point and then returns back to it. The external entry point must then return these values using the standard unknown-values convention. – In a more-arg entry point we don't know how many stack arguments there are at the beginning of the frame, so we can't really use the frame allocated by the external entry point at all. Instead we do a local call to the more-arg entry point, passing in a pointer to the first extra value. When the function returns, we deallocate the crap on the stack and then return the values. It is still o.k. to use the known-values return convention from the more-arg entry since the extra arg values are no longer needed by the time the returning function stores the return values back into the external entry point frame.
In full call we must always use the unknown-values convention for return. The first <n> values are passed in the standard argument registers. The Old-Cont register holds the Start of the values block and SP points to the End.
small, fast call (function arg0 ... arg<n>) "nargs" => value small, fast call-named (arg0 ... arg<n>) "nargs" "name" => value Call-Closure calls Function with the specified register arguments, returning the first value as the result. "nargs" is the total number of arguments passed. Only the register arguments actually passed should be specified as operands.
Call-Named is similar, but calls a global function specified at compile time by "name".
small, fast tail-call (function pc arg0 ... arg<n>) "nargs" small, fast tail-call-named (pc arg0 ... arg<n>) "nargs" "name" Similar to the standard call VOPs, but passes PC as the return PC, rather than returning to the call site. These VOPs have no results since they don't return.
small, fast multiple-call (function arg0 ... arg<n>) "nargs" => start end val0 ... val<n> small, fast multiple-call-named (arg0 ... arg<n>) "nargs" "name" => start end val0 ... val<n> These VOPs are similar to the standard call VOPs, but allow any number of values to be received by returning all the value passing registers as results. A specific number of values may be received by using Default-Values.
call-unknown (function count arg0 ... arg<n>) => start end val0 ... val<n> tail-call-unknown (function pc count arg0 ... arg<n>) Call a function with an unknown number of arguments. Used for apply and hairy multiple-value-call.
Function-Entry () "function" => env return-pc old-cont arg* This marks the place where we jump into a component for an external entry point. It represents whatever magic is necessary to do argument count checking and dispatching. The external entry points for each argument count will be successors of the entry-vector block (might be in the same block if only one?)
Function-Entry also represents argument passing by specifying the actual external passing locations as results, thus marking the beginning of their lifetimes. All passing locations actually used by any entry point are specified as Args, including stack arguments. ### Do we really need this? If we do, then we probably also need similar entry markers for local functions. The lifetimes don't really need to be explicitly bounded, since an entry point is effectively "the end of the world."